//////////////////////////////////////////////////////////////////////
// OpenTibia - an opensource roleplaying game
//////////////////////////////////////////////////////////////////////
//
//////////////////////////////////////////////////////////////////////
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//////////////////////////////////////////////////////////////////////
#include "otpch.h"

#include "configmanager.h"
#include <iostream>

ConfigManager::ConfigManager()
{
	L = NULL;

	m_isLoaded = false;

	m_confString[IP] = "";
	m_confInteger[ADMIN_PORT] = 0;
	m_confInteger[GAME_PORT] = 0;
	m_confInteger[LOGIN_PORT] = 0;
	m_confInteger[STATUS_PORT] = 0;
}

bool ConfigManager::loadFile(const std::string& _filename)
{
	if(L)
		lua_close(L);

	L = lua_open();

	if(!L) return false;

	if(luaL_dofile(L, _filename.c_str()))
	{
		lua_close(L);
		L = NULL;
		return false;
	}

	// parse config
	if(!m_isLoaded) // info that must be loaded one time (unless we reset the modules involved)
	{
		m_confString[CONFIG_FILE] = _filename;

		// These settings might have been set from command line
		if(m_confString[IP] == "")
			m_confString[IP] = getGlobalString(L, "ip", "127.0.0.1");
			
		if(m_confInteger[GAME_PORT] == 0)
			m_confInteger[GAME_PORT] = getGlobalNumber(L, "gamePort", 7172);
			
		if(m_confInteger[ADMIN_PORT] == 0)
			m_confInteger[ADMIN_PORT] = getGlobalNumber(L, "adminPort", 7171);
			
		if(m_confInteger[LOGIN_PORT] == 0)
			m_confInteger[LOGIN_PORT] = getGlobalNumber(L, "loginPort", 7171);
			
		if(m_confInteger[STATUS_PORT] == 0)
			m_confInteger[STATUS_PORT] = getGlobalNumber(L, "statusPort", 7171);

		unsigned int pos = _filename.rfind("/");
		std::string configPath = "";
		if(pos != std::string::npos)
			configPath = _filename.substr(0, pos+1);

		m_confString[DATA_DIRECTORY] = getGlobalString(L, "dataDirectory", "data/");
		m_confString[MAP_FILE] = getGlobalString(L, "mapName");

		m_confString[PASSWORD_TYPE_STR] = getGlobalString(L, "passwordType", "plain");
		m_confString[PASSWORD_SALT] = getGlobalString(L, "passwordSalt", "");
		m_confString[SQL_HOST] = getGlobalString(L, "sqlHost", "localhost");      
		m_confString[SQL_USER] = getGlobalString(L, "sqlUser", "root");
		m_confString[SQL_PASS] = getGlobalString(L, "sqlPassword", "");
		m_confString[SQL_DB] = getGlobalString(L, "sqlDatabase", "brazilian");
		m_confString[SQLITE_DB] = getGlobalString(L, "sqliteDatabase", "brazilian.s3db");
		m_confString[SQL_TYPE] = getGlobalString(L, "sqlType", "mysql");
		m_confInteger[SQL_PORT] = getGlobalNumber(L, "sqlPort", 3306);
		m_confInteger[PASSWORD_TYPE] = PASSWORD_TYPE_PLAIN;
	}

	m_confString[LOGIN_MSG] = getGlobalString(L, "loginMessage", "Welcome to Brazilian Server.");
	m_confString[SERVER_NAME] = getGlobalString(L, "serverName", "Brazilian");
	m_confString[OWNER_NAME] = getGlobalString(L, "ownerName", "Trylelr");
	m_confString[OWNER_EMAIL] = getGlobalString(L, "ownerEmail", "@hotmail.com");
	m_confString[URL] = getGlobalString(L, "url", "");
	m_confString[LOCATION] = getGlobalString(L, "location", "Brazil");
	m_confString[MAP_STORAGE_TYPE] = getGlobalString(L, "mapStorageType", "relational");
	m_confString[HOUSE_RENT_PERIOD] = getGlobalString(L, "houseRentPeriod", "monthly");
	m_confString[MOTD] = getGlobalString(L, "motd");
	m_confString[DEFAULT_PRIORITY] = getGlobalString(L, "defaultPriority", "high");
	m_confString[WORLD_TYPE] = getGlobalString(L, "worldType", "open");
	m_confString[OTSERV_VERSION_STRING] = getGlobalString(L, "serverStringVersion", "This server requires client version 8.70.");
	
	m_confInteger[LOGIN_TRIES] = getGlobalNumber(L, "loginTries", 5);
	m_confInteger[RETRY_TIMEOUT] = getGlobalNumber(L, "retryTimeout", 30 * 1000);
	m_confInteger[LOGIN_TIMEOUT] = getGlobalNumber(L, "loginTimeout", 5 * 1000);
	m_confInteger[STATUSQUERY_TIMEOUT] = getGlobalNumber(L, "statusTimeout", 30 * 1000);
	m_confInteger[MAX_PLAYERS] = getGlobalNumber(L, "maxPlayers", 1000);
	m_confInteger[MAX_MESSAGEBUFFER] = getGlobalNumber(L, "maxMessageBuffer", 4);
	m_confInteger[LOGIN_ATTACK_DELAY] = getGlobalNumber(L, "loginAttackDelay", 10 * 1000);
	m_confInteger[IDLE_TIME] = getGlobalNumber(L, "maxIdleTime", 16 * 60 *1000);
	m_confInteger[IDLE_TIME_WARNING] = getGlobalNumber(L, "maxIdleTimeWarning", 15 *60 * 1000);
	m_confInteger[CRITICAL_DAMAGE_CHANCE] = getGlobalNumber(L, "criticalDamageChance", 5);
	m_confInteger[EXHAUSTED] = getGlobalNumber(L, "exhausted", 30000);
	m_confInteger[EXHAUSTED_ADD] = getGlobalNumber(L, "exhaustedAdd", 5000);
	m_confInteger[COMBAT_EXHAUSTED] = getGlobalNumber(L, "fightExhausted", 2000);
	m_confInteger[HEAL_EXHAUSTED] = getGlobalNumber(L, "healExhausted", 1000);
	m_confInteger[STAIRHOP_EXHAUSTED] = getGlobalNumber(L, "stairhopExhausted", 2 * 1000);
	m_confInteger[IN_FIGHT_DURATION] = getGlobalNumber(L, "inFightDuration", 60 * 1000);
	m_confInteger[HUNTING_KILL_DURATION] = getGlobalNumber(L, "huntingKillDuration", 60 * 1000);
	m_confInteger[FIELD_OWNERSHIP_DURATION] = getGlobalNumber(L, "fieldOwnerShipDuration", 5 * 1000);
	m_confInteger[MIN_ACTIONTIME] = getGlobalNumber(L, "minActionInterval", 200);
	m_confInteger[MIN_ACTIONEXTIME] = getGlobalNumber(L, "minActionExInterval", 1000);
	m_confInteger[PUSH_INTERVAL] = getGlobalNumber(L, "pushCreatureInterval", 2000);
	m_confInteger[PARTY_MEMBER_EXP_BONUS] = getGlobalNumber(L, "partyExpMultiplier", 5);
	m_confInteger[RATE_EXPERIENCE_PVP] = getGlobalNumber(L, "rateExpPvp", 0);
	m_confInteger[RATE_EXPERIENCE] = getGlobalNumber(L, "rateExperience", 1);
	m_confInteger[RATE_SKILL] = getGlobalNumber(L, "rateSkill", 1);
	m_confInteger[RATE_LOOT] = getGlobalNumber(L, "rateLoot", 1);
	m_confInteger[RATE_MAGIC] = getGlobalNumber(L, "rateMagic", 1);
	m_confInteger[RATE_SPAWN] = getGlobalNumber(L, "rateSpawn", 1);
	m_confInteger[DEFAULT_DESPAWNRANGE] = getGlobalNumber(L, "deSpawnRange", 2);
	m_confInteger[DEFAULT_DESPAWNRADIUS] = getGlobalNumber(L, "deSpawnRadius", 50);
	m_confInteger[RATE_STAMINA_LOSS] = getGlobalNumber(L, "rateStaminaLoss", 1);
	m_confInteger[RATE_STAMINA_GAIN] = getGlobalNumber(L, "rateStaminaGain", 334);
	m_confInteger[SLOW_RATE_STAMINA_GAIN] = getGlobalNumber(L, "slowRateStaminaGain", 84);
	m_confInteger[STAMINA_EXTRA_EXPERIENCE_DURATION] = getGlobalNumber(L, "staminaExtraExperienceDuration", 2 * 60 * 60 * 1000);
	m_confFloat[STAMINA_EXTRA_EXPERIENCE_RATE] = getGlobalFloat(L, "staminaExtraExperienceRate", 0.5);
	m_confInteger[UNJUST_SKULL_DURATION] = getGlobalNumber(L, "unjustSkullDuration", 15 * 60 * 1000);
	m_confInteger[KILLS_PER_DAY_RED_SKULL] = getGlobalNumber(L, "killsPerDayRedSkull", 3);
	m_confInteger[KILLS_PER_WEEK_RED_SKULL] = getGlobalNumber(L, "killsPerWeekRedSkull", 5);
	m_confInteger[KILLS_PER_MONTH_RED_SKULL] = getGlobalNumber(L, "killsPerMonthRedSkull", 10);
	m_confInteger[KILLS_PER_DAY_BLACK_SKULL] = getGlobalNumber(L, "killsPerDayBlackSkull", 6);
	m_confInteger[KILLS_PER_WEEK_BLACK_SKULL] = getGlobalNumber(L, "killsPerWeekBlackSkull", 10);
	m_confInteger[KILLS_PER_MONTH_BLACK_SKULL] = getGlobalNumber(L, "killsPerMonthBlackSkull", 20);
	m_confInteger[RED_SKULL_DURATION] = getGlobalNumber(L, "redSkullDuration", 30 * 24 * 60 * 60);
	m_confInteger[BLACK_SKULL_DURATION] = getGlobalNumber(L, "blackSkullDuration", 45 * 24 * 60 * 60);
	m_confInteger[BLACK_SKULL_DEATH_HEALTH] = getGlobalNumber(L, "blackSkulledDeathHealth", 40);
	m_confInteger[BLACK_SKULL_DEATH_MANA] = getGlobalNumber(L, "blackSkulledDeathMana", 0);
	m_confInteger[HOUSE_LEVEL] = getGlobalNumber(L, "houseLevel", 1);
	m_confInteger[HOUSE_TILE_PRICE] = getGlobalNumber(L, "houseTilePrice", 100);
	m_confInteger[NOTATIONS_TO_BAN] = getGlobalNumber(L, "notationsToBanishment", 3);
	m_confInteger[WARNINGS_TO_FINALBAN] = getGlobalNumber(L, "warningsToFinalBanishment", 4);
	m_confInteger[WARNINGS_TO_DELETION] = getGlobalNumber(L, "warningsToDeletation", 5);
	m_confInteger[BAN_LENGTH] = getGlobalNumber(L, "banishmentLength", 7 * 86400);
	m_confInteger[FINALBAN_LENGTH] = getGlobalNumber(L, "finalBanishmentLength", 30 * 86400);
	m_confInteger[IPBANISHMENT_LENGTH] = getGlobalNumber(L, "ipBanishmentLength", 86400);
	m_confInteger[FIST_STRENGTH] = getGlobalNumber(L, "fistStrength", 7);
	m_confInteger[GUILD_WAR_FEE] = getGlobalNumber(L, "guildWarFee", 1000);
	m_confString[DEATH_MSG] = getGlobalString(L, "deathMessage", "You are dead.");
	m_confInteger[MIN_PVP_LEVEL] = getGlobalNumber(L, "minPvpLevel", 0);
	m_confInteger[TRADE_ITEMS_LIMIT] = getGlobalNumber(L, "tradeItemsLimit", 100);
	m_confInteger[HEIGHT_MINIMUM_FOR_IDLE] = getGlobalNumber(L, "heightMinimumForIdle", 2);
	m_confInteger[MAX_PLAYER_SUMMON] = getGlobalNumber(L, "maxSummonsPerPlayer", 2);
	m_confInteger[DEATH_ASSIST_COUNT] = getGlobalNumber(L, "deathAssistCount", 1);
	m_confInteger[NPC_MAX_NONESTACKABLE_SELL_AMOUNT] = getGlobalNumber(L, "npcMaxNoneStackableSellAmount", 100);
	m_confInteger[SERVERSAVE_H] = getGlobalNumber(L, "serverSaveHour", 8);
	m_confInteger[AUTOSAVE_TIME] = getGlobalNumber(L, "autoSaveInterval", 30 * 60 * 1000);
	m_confInteger[OTSERV_MIN_VERSION] = getGlobalNumber(L, "serverMinClientVersion", 870);
	m_confInteger[OTSERV_MAX_VERSION] = getGlobalNumber(L, "serverMaxClientVersion", 870);
	
	#ifdef __MIN_PVP_LEVEL_APPLIES_TO_SUMMONS__
	m_confBool[MIN_PVP_LEVEL_APPLIES_TO_SUMMONS] = getGlobalBool(L, "minPvpLevelAppliesToSummons", true);
	#endif
	m_confBool[USE_HOTKEYS] = getGlobalBool(L, "enableHotkeys", false);
	m_confBool[LAST_HIT_PZBLOCK_ONLY] = getGlobalBool(L, "lastHitPzBlockOnly", true);
	m_confBool[DEFENSIVE_PZ_LOCK] = getGlobalBool(L, "defensivePzLockock", false);
	m_confBool[DISPLAY_CRITICAL_DAMAGE] = getGlobalBool(L, "displayCriticalDamage", false);
	m_confBool[ALLOW_CHANGE_OUTFIT] = getGlobalBool(L, "allowChangeOutfit", true);
	m_confBool[OUTFIT_DAMAGE] = getGlobalBool(L, "feetDamage", true);
	m_confBool[ENABLE_CAP] = getGlobalBool(L, "enableCapSystem", true);
	m_confBool[ALLOW_CLONES] = getGlobalBool(L, "allowClones", false);
	m_confBool[ONE_PLATER_ACCOUNT] = getGlobalBool(L, "onePlayerPerAccount", true);
	m_confBool[RATES_FOR_PLAYER_KILLING] = getGlobalBool(L, "ratesForPlayerKilling", false);
	m_confBool[STAMINA_EXTRA_EXPERIENCE_ONLYPREM] = getGlobalBool(L, "staminaExtraExperiencePremium", true);
	m_confBool[BROADCAST_BANISHMENTS] = getGlobalBool(L, "broadcastBanishments", false);
	m_confBool[ALLOW_GAMEMASTER_MULTICLIENT] = getGlobalBool(L, "allowGamemasterMulticlient", false);
	m_confBool[ADDONS_ONLY_FOR_PREMIUM] = getGlobalBool(L, "addonsOnlyForPremium", true);
	m_confBool[MONSTER_SPAWN_WALKBACK] = getGlobalBool(L, "monsterSpawnWalkBack", true);
	m_confBool[FOLLOW_SUMMONS_PLAYERS] = getGlobalBool(L, "summonsCanFollowPlayer", false);
	m_confBool[REMOVE_AMMUNITION] = getGlobalBool(L, "removeAmmunitions", true);
	m_confBool[REMOVE_RUNE_CHARGES] = getGlobalBool(L, "removeRuneCharges", true);
	m_confBool[REMOVE_WEAPON_CHARGES] = getGlobalBool(L, "removeWeaponsCharges", true);
	m_confBool[DISTANCE_WEAPON_INTERRUPT_SWING] = getGlobalBool(L, "distanceWeaponInterruptSwing", true);
	m_confBool[WANDS_INTERRUPT_SWING] = getGlobalBool(L, "wandsAndRodsInterruptSwing", true);
	m_confBool[SHOW_NEW_SKILL_LEVEL] = getGlobalBool(L, "showNewSkillLevel", false);
	m_confBool[SHOW_HEALING] = getGlobalBool(L, "showHealingDamage", false);
	m_confBool[SHOW_MANA_DAMAGE] = getGlobalBool(L, "showManaDamage", false);
	m_confBool[SHOW_MONSTER_HEALING] = getGlobalBool(L, "showMonstersHealing", false);
	m_confBool[ORANGE_SPELL_TEXT] = getGlobalBool(L, "orangeSpellText", false);
	m_confBool[SHOW_DEATH_WINDOW] = getGlobalBool(L, "showDeathWindow", true);
	m_confBool[CAN_ATTACK_INVISIBLE] = getGlobalBool(L, "canAttackInvisible", false);
	m_confBool[CAN_PASS_THROUGH] = getGlobalBool(L, "canPassThrough", true);
	m_confBool[EXPERIENCE_STAGES] = getGlobalBool(L, "experienceStages", false);
	m_confBool[ENABLE_MOUNTS] = getGlobalBool(L, "enableMounts", true);
	m_confBool[ENABLE_COOLDOWN] = getGlobalBool(L, "enableCooldown", true);
	m_confBool[USE_BALANCE_HOUSE_PAYING] = getGlobalBool(L, "useBalanceHousePaying", true);
	m_confBool[PREMIUM_ONLY_BEDS] = getGlobalBool(L, "onlyPremiumUseBeds", true);
	m_confBool[SHOW_HOUSE_PRICES] = getGlobalBool(L, "showHousePrices", false);
	m_confBool[HOUSE_ONLY_PREMIUM] = getGlobalBool(L, "houseOnlyPremium", true);
	m_confBool[SERVERSAVE_ENABLED] = getGlobalBool(L, "serverSaveEnabled", true);
	m_confBool[AUTOSAVE_ENABLED] = getGlobalBool(L, "autoSaveEnabled", true);
	m_confBool[SHUTDOWN_AT_SERVERSAVE] = getGlobalBool(L, "shutdownAtServerSave", false);
	m_confBool[CLEAN_MAP_AT_SERVERSAVE] = getGlobalBool(L, "cleanMapAtServerSave", true);
	m_confBool[MW_DISAPPEAR_ON_WALK] = getGlobalBool(L, "magicWallDisappearOnWalk", true);
	m_confBool[FREE_PREMIUM] = getGlobalBool(L, "freePremium", false);

	m_isLoaded = true;
	return true;
}

bool ConfigManager::reload()
{
	if(!m_isLoaded)
		return false;

	return loadFile(m_confString[CONFIG_FILE]);
}

const std::string& ConfigManager::getString(uint32_t _what) const
{
	if(m_isLoaded && _what < LAST_STRING_CONFIG)
		return m_confString[_what];
	else
	{
		std::cout << "Warning: [ConfigManager::getString] " << _what << std::endl;
		return m_confString[DUMMY_STR];
	}
}

bool ConfigManager::getBool(uint32_t _what) const
{
	if(m_isLoaded && _what < LAST_BOOL_CONFIG)
		return m_confBool[_what];
	else
	{
		std::cout << "Warning: [ConfigManager::getBool] " << _what << std::endl;
		return false;
	}
}

int64_t ConfigManager::getNumber(uint32_t _what) const
{
	if(m_isLoaded && _what < LAST_INTEGER_CONFIG)
		return m_confInteger[_what];
	else
	{
		std::cout << "Warning: [ConfigManager::getNumber] " << _what << std::endl;
		return 0;
	}
}

double ConfigManager::getDouble(uint32_t _what) const
{
	if(m_isLoaded && _what < LAST_FLOAT_CONFIG)
		return m_confFloat[_what];
	else
	{
		std::cout << "Warning: [ConfigManager::getDouble] " << _what << std::endl;
		return 0;
	}
}

bool ConfigManager::setNumber(uint32_t _what, int64_t _value)
{
	if(_what < LAST_INTEGER_CONFIG)
	{
		m_confInteger[_what] = _value;
		return true;
	}
	else
	{
		std::cout << "Warning: [ConfigManager::setNumber] " << _what << std::endl;
		return false;
	}
}

bool ConfigManager::setString(uint32_t _what, const std::string& _value)
{
	if(_what < LAST_STRING_CONFIG)
	{
		m_confString[_what] = _value;
		return true;
	}
	else
	{
		std::cout << "Warning: [ConfigManager::setString] " << _what << std::endl;
		return false;
	}
}

std::string ConfigManager::getGlobalString(lua_State* _L, const std::string& _identifier, const std::string& _default)
{
	lua_getglobal(_L, _identifier.c_str());

	if(!lua_isstring(_L, -1)){
		lua_pop(_L, 1);
		return _default;
	}

	int len = (int)lua_strlen(_L, -1);
	std::string ret(lua_tostring(_L, -1), len);
	lua_pop(_L,1);

	return ret;
}

int64_t ConfigManager::getGlobalNumber(lua_State* _L, const std::string& _identifier, int64_t _default)
{
	lua_getglobal(_L, _identifier.c_str());

	if(!lua_isnumber(_L, -1)){
		lua_pop(_L, 1);
		return _default;
	}

	int64_t val = (int64_t)lua_tonumber(_L, -1);
	lua_pop(_L,1);

	return val;
}

double ConfigManager::getGlobalFloat(lua_State* _L, const std::string& _identifier, double _default)
{
	lua_getglobal(_L, _identifier.c_str());

	if(!lua_isnumber(_L, -1)){
		lua_pop(_L, 1);
		return _default;
	}

	double val = lua_tonumber(_L, -1);
	lua_pop(_L,1);

	return val;
}

bool ConfigManager::getGlobalBool(lua_State* _L, const std::string& _identifier, bool _default)
{
	lua_getglobal(_L, _identifier.c_str());

	if(lua_isnumber(_L, -1)){
		int val = (int)lua_tonumber(_L, -1);
		lua_pop(_L, 1);
		return val != 0;
	} else if(lua_isstring(_L, -1)){
		std::string val = lua_tostring(_L, -1);
		lua_pop(_L, 1);
		return val == "yes";
	} else if(lua_isboolean(_L, -1)){
		bool v = lua_toboolean(_L, -1) != 0;
		lua_pop(_L, 1);
		return v;
	}

	return _default;
}

void ConfigManager::getConfigValue(const std::string& key, lua_State* toL)
{
	lua_getglobal(L, key.c_str());
	moveValue(L, toL);
}

void ConfigManager::moveValue(lua_State* from, lua_State* to)
{
	switch(lua_type(from, -1)){
		case LUA_TNIL:
			lua_pushnil(to);
			break;
		case LUA_TBOOLEAN:
			lua_pushboolean(to, lua_toboolean(from, -1));
			break;
		case LUA_TNUMBER:
			lua_pushnumber(to, lua_tonumber(from, -1));
			break;
		case LUA_TSTRING:
		{
			size_t len;
			const char* str = lua_tolstring(from, -1, &len);
			lua_pushlstring(to, str, len);
		}
			break;
		case LUA_TTABLE:
			lua_newtable(to);

			lua_pushnil(from); // First key
			while(lua_next(from, -2)){
				// Move value to the other state
				moveValue(from, to);
				// Value is popped, key is left

				// Move key to the other state
				lua_pushvalue(from, -1); // Make a copy of the key to use for the next iteration
				moveValue(from, to);
				// Key is in other state.
				// We still have the key in the 'from' state ontop of the stack

				lua_insert(to, -2); // Move key above value
				lua_settable(to, -3); // Set the key
			}
		default:
			break;
	}
	// Pop the value we just read
	lua_pop(from, 1);
}
